#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>
#include <mpi.h>
#include <ctype.h>
#define MAXPAL 1000
#define MAXPRI 10000

typedef struct vetorPalavra
{
    char *p;
} vetorPalavra;

#define _FFS(x)

int TAMBLOCO;

void escreve_bloco(FILE *arqIn, FILE *arqOut, int offset) {
    char frase[100000];
    int cont=0;
    for(cont = 0; ((offset!=3 && cont < TAMBLOCO) && !feof(arqIn)) || (offset==3 && !feof(arqIn)); cont++) {
        if(fgets(frase, 10000, arqIn) == NULL) {
            return;
        }
        if(fputs(frase, arqOut )==EOF) {
            printf("Erro na escrita do arquivo\n\n");
        }
    }
}
int contaLinhas(FILE *fp) {
    char frase[100000];
    int cont=0;
    while(fgets(frase, 100000, fp) != NULL && !feof(fp)) {
        cont++;
    }
    return cont;
}
int separa_wiki(char * arqEntrada) {
    int i=0;
    char * nome[20];
    FILE * fp, *arqSaida1, *arqSaida2, *arqSaida3, *arqSaida4;

    fp = fopen (arqEntrada, "r");
    if(!fp) {
        printf("Erro ao abrir o arquivo de entrada\n\n");
        return -1;
    }

    arqSaida1 = fopen ("arqWiki1.txt", "w");
    arqSaida2 = fopen ("arqWiki2.txt", "w");
    arqSaida3 = fopen ("arqWiki3.txt", "w");
    arqSaida4 = fopen ("arqWiki4.txt", "w");

    if(!arqSaida1 || !arqSaida2 || !arqSaida3 || !arqSaida4) {
        printf("Erro ao criar novo arquivo! \n\n");
        exit(-1);
    }

    rewind(fp);
    TAMBLOCO = ceil(contaLinhas(fp)/4);
    fseek(fp, 0, SEEK_END);

    rewind(fp);

    escreve_bloco(fp, arqSaida1, 0);
    escreve_bloco(fp, arqSaida2, 1);
    escreve_bloco(fp, arqSaida3, 2);
    escreve_bloco(fp, arqSaida4, 3);

    fclose(fp);
    fclose(arqSaida1);
    fclose(arqSaida2);
    fclose(arqSaida3);
    fclose(arqSaida4);

    return 0;
}
int separa_shake(char * arqEntrada) {
    int i=0;
    FILE * fp, *arqSaida1, *arqSaida2, *arqSaida3, *arqSaida4;

    fp = fopen (arqEntrada, "r");
    if(!fp) {
        printf("Erro ao abrir o arquivo de entrada\n\n");
        return -1;
    }

    arqSaida1 = fopen ("arqShake1.txt", "w");
    arqSaida2 = fopen ("arqShake2.txt", "w");
    arqSaida3 = fopen ("arqShake3.txt", "w");
    arqSaida4 = fopen ("arqShake4.txt", "w");

    if(!arqSaida1 || !arqSaida2 || !arqSaida3 || !arqSaida4) {
        printf("Erro ao criar novo arquivo! \n\n");
        exit(-1);
    }
    rewind(fp);
    TAMBLOCO = ceil(contaLinhas(fp)/4);
    fseek(fp, 0, SEEK_END);

    rewind(fp);

    escreve_bloco(fp, arqSaida1, 0);
    escreve_bloco(fp, arqSaida2, 1);
    escreve_bloco(fp, arqSaida3, 2);
    escreve_bloco(fp, arqSaida4, 3);

    fclose(fp);
    fclose(arqSaida1);
    fclose(arqSaida2);
    fclose(arqSaida3);
    fclose(arqSaida4);

    return 0;
}
/***************************************************************************************************/
vetorPalavra* separaPalavra(char palavra[])
{
    struct vetorPalavra vet[200];
    int i = 0;

    (vet[i]).p = strtok(palavra, ".():;,'?!*-|/%[]><+#");

    while((vet[i]).p != NULL)
    {
        i++;
        (vet[i]).p = strtok(NULL, ".():;,'?!*-|/%[]><+#");
    }
    return vet;
}

int palindromo(char *string, int tamanho) {
    int i = tamanho-1, j=0;
    for(j=0; j<(tamanho/2); j++, i--) {
        //48 = '0'    57 = '9'     97 = 'a'    122 = 'z'
        if ((tolower(string[j]) < 48 || tolower(string[j]) > 57 && tolower(string[j]) < 97 || tolower(string[j]) > 122) && (j+1<i))//se nao for letra valida, pula
            j++;
        if ((tolower(string[i]) < 48 || tolower(string[i]) > 57 && tolower(string[i]) < 97 || tolower(string[i]) > 122) && (i-1>=j))//se nao for letra valida, pula
            i--;
        if (tolower(string[j])!= tolower(string[i])) {
            return 0;//nao e palindromo
        }
    }
    return 1;//e palindromo
}

void anulaMultiplos(int primo[], int m, int sub) {
    int i=0;
    for(i = (sub+1); i <= m; i++) {
        if((i%sub) == 0)
            primo[i] = 0;
    }
}

int *crivoErastotenes(int max) {
    int i=0;
    int primo[max];
    for (i = 1; i <= max; i++)
        primo[i] = 1;//inicia o vetor com 1's
    for (i = 2; i <= max; i++)
        if (primo[i] == 1)//evitar processar mais de uma vez
            anulaMultiplos(primo, max, i);//anula multiplos
    return primo;
}

int verificaPrimo(char *string, int tamanho, int *primos) {
    int i=0, soma = 0;
    //faz a soma do ascii
    for(i=0; i < tamanho; i++)
        soma = soma + string[i];
    /*verifica se o numero e primo utilizando o vetor gerado pelo crivo*/
    if(primos[soma]==1)
        return 1;//se for primo retorna 1
    else return 0;//0 c.c
}

int palindromoPalavraPrimo(int *primos, FILE *fp, double *tempoPa, double *tempoPr,  unsigned int *numPalindromos, unsigned int *numPrimos) {
    char pa[MAXPAL]="\0";
    vetorPalavra *aux;
    int size=0, retorno=0, i=0;
    clock_t inicioPa = 0 ,fimPa = 0, inicioPr = 0,fimPr = 0;

    inicioPa=clock();
    rewind(fp);//muda o ponteiro para o comeco do arquivo
    while(fscanf(fp,"%s",pa)!=EOF) {
        strcat(pa, "\0");
        aux = separaPalavra(pa);
        for(i=0; aux[i].p != NULL; i++) {
            size = strlen(aux[i].p);//pega o tamanho
            retorno = palindromo(aux[i].p, size);//funcao q retorna 1 se for palindromo e 0 c.c.
            *numPalindromos = *numPalindromos + retorno;//atualiza numero total de palindromos

            /*se for palindromo, calcula a soma do ascii*/
            if(retorno == 1) {
                inicioPr=clock();
                retorno = verificaPrimo(aux[i].p, size, primos);//funcao q retorna 1 se for primo e 0 c.c.
                *numPrimos = *numPrimos + retorno;
                fimPr=clock();
                *tempoPr = *tempoPr + ((fimPr-inicioPr)/(CLOCKS_PER_SEC/1000));
            }
        }
    }
    fimPa=clock();
    *tempoPa = ((fimPa-inicioPa)/(CLOCKS_PER_SEC/1000)) - *tempoPr;
    return 0;
}

int palindromoPalavra(FILE *fp, double * tempoPa, unsigned int * numPalindromos) {
    char pa[MAXPAL] ="\0";
    vetorPalavra *aux;
    int size=0, retorno=0, i=0;
    clock_t inicioPa = 0 ,fimPa = 0;

    inicioPa=clock();
    rewind(fp);//muda o ponteiro para o comeco do arquivo
    while(fscanf(fp,"%s",pa)!=EOF) {
        aux = separaPalavra(pa);
        for(i=0; aux[i].p != NULL; i++) {
            size = strlen(aux[i].p);//pega o tamanho
            retorno = palindromo(aux[i].p, size);//funcao q retorna 1 se for palindromo e 0 c.c.
            *numPalindromos = *numPalindromos + retorno;//atualiza numero total de palindromos
        }
    }
    fimPa=clock();
    *tempoPa = ((fimPa-inicioPa)/(CLOCKS_PER_SEC/1000));

    return 0;
}


int palindromoFrase(FILE *fp, double * tempoFr, unsigned int * numFrasePal) {
    char frase[1000]="\0";
    int tamanho=0,retorno=0;
    clock_t inicioFr = 0 ,fimFr = 0;

    inicioFr = clock();
    rewind(fp);//muda o ponteiro para o comeco do arquivo
    while(fgets(frase,1000,fp)!=NULL) { //pega string
        if(strcmp(frase,"\n")==0)
            continue;
        tamanho=strlen(frase);//pega o tamanho
        retorno = palindromo(frase,tamanho);
        *numFrasePal = *numFrasePal + retorno;
    }
    fimFr=clock();
    *tempoFr = ((fimFr-inicioFr)/(CLOCKS_PER_SEC/1000));
    return 0;
}

void funcao_arquivo1(const char * arq, int * primos, double * tempoPa, double * tempoPr, unsigned int *numPalindromos, unsigned int * numPrimos) {

    int soma = 0,i,j=0;
    FILE *fp, *fp2;

    fp=fopen(arq, "r");
    if(!fp) {
        printf("Erro ao abrir o arquivo\n\n");
        exit(-1);
    }
    palindromoPalavraPrimo(primos, fp, tempoPa, tempoPr, numPalindromos, numPrimos);
    fclose(fp);
}
void funcao_arquivo2(char * arqEntrada, double * tempoPa, double * tempoFr, unsigned int * numPalindromo, unsigned int * numPalFrase) {
    int soma = 0,i,j=0;
    FILE *fp2;

    fp2 = fopen(arqEntrada, "r");
    if(!fp2) {
        printf("Erro ao abrir o arquivo sheakespeare\n\n");
        exit(0);
    }
    palindromoFrase(fp2, tempoFr, numPalFrase);
    palindromoPalavra(fp2, tempoPa, numPalindromo);

    fclose(fp2);
}
/****************************************************************************************************/
typedef struct {
    unsigned int numPalindromos;
    unsigned int numPrimos;
    double tempoPa;
    double tempoPr;
} Wiki;
typedef struct {
    unsigned int numPalindromos;
    unsigned int numPalFrase;
    double tempoPa;
    double tempoFr;
} Shake;

int main(int argc, char *argv[]) {
    int acc=0, i=0;
    int rank = 0, *primos;
    unsigned int numPalindromo=0, numPrimo=0, rc;
    double tempoPa = 0.0, tempoPr = 0.0, accD=0.0;

    unsigned int numPal1=0, numPalFr=0;
    double tempoPa1=0.0, tempoFr=0.0;

    clock_t inicioTotal, fimTotal;
    double tempoTotal=0.0;

    MPI_Status Stat;

    Wiki wikipedia[4];
    Shake shakespeare[4];

    argv[2] = "4";

    separa_wiki("./data-t2/wikipedia.txt");
    separa_shake("./data-t2/shakespe.txt");
    inicioTotal=clock();
    /*funcao que inicializa a região paralela com mpi*/
    rc=MPI_Init(&argc, &argv);
    if(rc != MPI_SUCCESS) {
        printf("Não inicializou com sucesso :(\n\n");
        return -1;
    }
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    primos = (int*)malloc(MAXPRI*sizeof(int));//aloca vetor para o crivo
    primos = crivoErastotenes(MAXPRI);

    if(rank ==0) {

        omp_set_num_threads(2);
#pragma omp parallel firstprivate(tempoPa, tempoPr, numPalindromo, numPrimo, tempoPa1, tempoFr, numPal1, numPalFr)
        {
#pragma omp sections
            {
#pragma omp section
                {
                    funcao_arquivo1("arqWiki1.txt", primos, &tempoPa, &tempoPr, &numPalindromo, &numPrimo);
                    wikipedia[0].numPalindromos = numPalindromo;
                    wikipedia[0].numPrimos = numPrimo;
                    wikipedia[0].tempoPa = tempoPa;
                    wikipedia[0].tempoPr = tempoPr;
                }
#pragma omp section
                {
                    funcao_arquivo2("arqShake1.txt", &tempoPa1, &tempoFr, &numPal1, &numPalFr);
                    shakespeare[0].numPalindromos = numPal1;
                    shakespeare[0].numPalFrase = numPalFr;
                    shakespeare[0].tempoPa = tempoPa1;
                    shakespeare[0].tempoFr = tempoFr;
                }
            }
#pragma omp barrier
        }
        MPI_Recv(&(wikipedia[1]).numPalindromos, 1, MPI_UNSIGNED, 1, 1, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&(wikipedia[1]).numPrimos, 1, MPI_UNSIGNED, 1, 1, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&wikipedia[1].tempoPa, 1, MPI_DOUBLE, 1, 1, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&wikipedia[1].tempoPr, 1, MPI_DOUBLE, 1, 1, MPI_COMM_WORLD, &Stat);

        MPI_Recv(&(shakespeare[1]).numPalindromos, 1, MPI_UNSIGNED, 1, 2, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&shakespeare[1].tempoPa, 1, MPI_DOUBLE, 1, 2, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&(shakespeare[1]).numPalFrase, 1, MPI_UNSIGNED, 1, 2, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&shakespeare[1].tempoFr, 1, MPI_DOUBLE, 1, 2, MPI_COMM_WORLD, &Stat);

        MPI_Recv(&wikipedia[2].numPalindromos, 1, MPI_UNSIGNED, 2, 1, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&wikipedia[2].numPrimos, 1, MPI_UNSIGNED, 2, 1, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&wikipedia[2].tempoPa, 1, MPI_DOUBLE, 2, 1, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&wikipedia[2].tempoPr, 1, MPI_DOUBLE, 2, 1, MPI_COMM_WORLD, &Stat);

        MPI_Recv(&(shakespeare[2]).numPalindromos, 1, MPI_UNSIGNED, 2, 2, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&shakespeare[2].tempoPa, 1, MPI_DOUBLE, 2, 2, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&(shakespeare[2]).numPalFrase, 1, MPI_UNSIGNED, 2, 2, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&shakespeare[2].tempoFr, 1, MPI_DOUBLE, 2, 2, MPI_COMM_WORLD, &Stat);

        MPI_Recv(&wikipedia[3].numPalindromos, 1, MPI_UNSIGNED, 3, 1, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&wikipedia[3].numPrimos, 1, MPI_UNSIGNED, 3, 1, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&wikipedia[3].tempoPa, 1, MPI_DOUBLE, 3, 1, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&wikipedia[3].tempoPr, 1, MPI_DOUBLE, 3, 1, MPI_COMM_WORLD, &Stat);

        MPI_Recv(&(shakespeare[3]).numPalindromos, 1, MPI_UNSIGNED, 3, 2, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&shakespeare[3].tempoPa, 1, MPI_DOUBLE, 3, 2, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&(shakespeare[3]).numPalFrase, 1, MPI_UNSIGNED, 3, 2, MPI_COMM_WORLD, &Stat);
        MPI_Recv(&shakespeare[3].tempoFr, 1, MPI_DOUBLE, 3, 2, MPI_COMM_WORLD, &Stat);
    }
    else if (rank ==1) {
        omp_set_num_threads(2);
#pragma omp parallel firstprivate(tempoPa, tempoPr, numPalindromo, numPrimo, tempoPa1, tempoFr, numPal1, numPalFr)
        {
#pragma omp sections
            {
#pragma omp section
                {
                    funcao_arquivo1("arqWiki2.txt", primos, &tempoPa, &tempoPr, &numPalindromo, &numPrimo);
                    MPI_Send(&numPalindromo, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD);
                    MPI_Send(&numPrimo, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD);
                    MPI_Send(&tempoPa, 1, MPI_DOUBLE, 0, 1, MPI_COMM_WORLD);
                    MPI_Send(&tempoPr, 1, MPI_DOUBLE, 0, 1, MPI_COMM_WORLD);
                }
#pragma omp section
                {
                    funcao_arquivo2("arqShake2.txt", &tempoPa1, &tempoFr, &numPal1, &numPalFr);
                    MPI_Send(&numPal1, 1, MPI_UNSIGNED, 0, 2, MPI_COMM_WORLD);
                    MPI_Send(&tempoPa1, 1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
                    MPI_Send(&numPalFr, 1, MPI_UNSIGNED, 0, 2, MPI_COMM_WORLD);
                    MPI_Send(&tempoFr, 1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
                }
            }
#pragma omp barrier
        }
    }
    else if (rank ==2) {
        omp_set_num_threads(2);
#pragma omp parallel firstprivate(tempoPa, tempoPr, numPalindromo, numPrimo, tempoPa1, tempoFr, numPal1, numPalFr)
        {
#pragma omp sections
            {
#pragma omp section
                {
                    funcao_arquivo1("arqWiki3.txt", primos, &tempoPa, &tempoPr, &numPalindromo, &numPrimo);
                    MPI_Send(&numPalindromo, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD);
                    MPI_Send(&numPrimo, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD);
                    MPI_Send(&tempoPa, 1, MPI_DOUBLE, 0, 1, MPI_COMM_WORLD);
                    MPI_Send(&tempoPr, 1, MPI_DOUBLE, 0, 1, MPI_COMM_WORLD);
                }
#pragma omp section
                {
                    funcao_arquivo2("arqShake3.txt", &tempoPa1, &tempoFr, &numPal1, &numPalFr);
                    MPI_Send(&numPal1, 1, MPI_UNSIGNED, 0, 2, MPI_COMM_WORLD);
                    MPI_Send(&tempoPa1, 1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
                    MPI_Send(&numPalFr, 1, MPI_UNSIGNED, 0, 2, MPI_COMM_WORLD);
                    MPI_Send(&tempoFr, 1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
                }
            }
#pragma omp barrier
        }
    }
    else if (rank ==3) {
        omp_set_num_threads(2);
#pragma omp parallel firstprivate(tempoPa, tempoPr, numPalindromo, numPrimo, tempoPa1, tempoFr, numPal1, numPalFr)
        {
#pragma omp sections
            {
#pragma omp section
                {
                    funcao_arquivo1("arqWiki4.txt", primos, &tempoPa, &tempoPr, &numPalindromo, &numPrimo);
                    MPI_Send(&numPalindromo, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD);
                    MPI_Send(&numPrimo, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD);
                    MPI_Send(&tempoPa, 1, MPI_DOUBLE, 0, 1, MPI_COMM_WORLD);
                    MPI_Send(&tempoPr, 1, MPI_DOUBLE, 0, 1, MPI_COMM_WORLD);
                }
#pragma omp section
                {
                    funcao_arquivo2("arqShake4.txt", &tempoPa1, &tempoFr, &numPal1, &numPalFr);
                    MPI_Send(&numPal1, 1, MPI_UNSIGNED, 0, 2, MPI_COMM_WORLD);
                    MPI_Send(&tempoPa1, 1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
                    MPI_Send(&numPalFr, 1, MPI_UNSIGNED, 0, 2, MPI_COMM_WORLD);
                    MPI_Send(&tempoFr, 1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
                }
            }
#pragma omp barrier
        }
    }

    rc = MPI_Barrier(MPI_COMM_WORLD);
    if(rc != MPI_SUCCESS) {
        printf("Barrier not succeed\n\n");
        return -1;
    }

    rc = MPI_Finalize();
    fimTotal=clock();
    if(rc == MPI_SUCCESS && rank == 0) {
        printf("Dados do arquivo wikipedia.txt:\n\n");
        acc=0;
        for(i=0; i<4; i++) {
            acc = acc + wikipedia[i].numPalindromos;
        }
        printf("Total de palindromos: %d\n", acc);

        acc=0;
        for(i=0; i<4; i++) {
            acc = acc + wikipedia[i].numPrimos;
        }
        printf("Total de numeros primos: %d\n", acc);

        for(i=0; i<4; i++) {
            accD = accD + wikipedia[i].tempoPa;
        }
        printf("Tempo utlizado em milisegundos no calculo do Palindromo: %lf.\n", accD);

        accD=0;
        for(i=0; i<4; i++) {
            accD = accD + wikipedia[i].tempoPr;
        }
        printf("Tempo utlizado em milisegundos no calculo do Primo: %lf.\n\n", accD);

        printf("Dados do arquivo shakespe.txt:\n\n");
        acc=0;
        for(i=0; i<4; i++) {
            acc = acc + shakespeare[i].numPalindromos;
        }
        printf("Total de palavras palindromos: %d\n", acc);
        acc=0;
        for(i=0; i<4; i++) {
            acc = acc + shakespeare[i].numPalFrase;
        }
        printf("Total de frases palindromos: %d\n", acc);

        for(i=0; i<4; i++) {
            accD = accD + shakespeare[i].tempoPa;
        }
        printf("Tempo utlizado em milisegundos no calculo da palavra palindromo: %lf.\n", accD);

        accD=0;
        for(i=0; i<4; i++) {
            accD = accD + shakespeare[i].tempoFr;
        }
        printf("Tempo utlizado em milisegundos no calculo da frase palindromo: %lf.\n\n", accD);

        tempoTotal = ((fimTotal-inicioTotal)/(CLOCKS_PER_SEC/1000));
        printf("Tempo Total utlizado em milisegundos %lf.\n\n",tempoTotal);
    }
    return 0;
}

